// Segundo trabalho de Programacao Concorrente
// Deteccao de Palindromos
// Daniel Hatanaka, 7277579
// Guilherme Kalatzis, 7239426
// Thiago Tanaka, 7153014
// Prof. Dr. Julio Estrella, Estagiario PAE Luiz Henrique Nunes
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<omp.h>

// Numero de threads a serem utilizadas - influencia no numero de partes em que arquivo de entrada sera dividido
#define NTHREADS 2

// Conjunto de caracteres que nao farao parte de palavras e frases, para que palindromos possam ser verificados
char ignoreList[45] = {EOF, '.', '\r', '\n', ' ', '(', ')', '-', '1', '2',
						'3', '4', '5', '6', '7', '8', '9', '0', '\'', ':',
						'\t', ',', ';', '!', '?', 9, '[', ']', '|', '&',
						'/', '\\', '*', '"', '+', '$', '@', '%', '<', '>',
						'^', '_', '~', '#', '='};

// Checa se uma string (passada por parametro) eh um palindromo
// Retorna 1 se verdadeiro e 0 caso contrario
int checkPalindrome(char *string) {
	// Utiliza dois marcadores, um no inicio e outro no final da string
	int begin = 0, end = strlen(string)-1;
	// Nao considera strings com um unico caractere como palindromos
	if (strlen(string) <= 1)
		return 0;
	// Enquanto os marcadores nao se cruzarem
	while (begin < end)
		// Percorre a string em sentido opostos comparando caracteres equidistantes, se forem diferentes nao eh um palindromo
		if(string[begin++] != string[end--])
			return 0;
	// Caso nenhum dos caracteres forem diferentes, a funcao nao tera retornado e a string entao eh um palindromo
	return 1;
}

// Conta numero de palavras e frases palindromas dentro de uma parte do arquivo de entrada (a entrada eh dividida para as threads)
// wordCount e phraseCount sao parametros passados por referencia, portanto mantem seus conteudos apos a chamada desta funcao
void countPalindromesInPart(char *part, int *wordCount, int *phraseCount) {
	int i, wordIndex = 0, phraseIndex = 0, partLength = strlen(part), words = 0, phrases = 0;
	char character, word[1024], phrase[10000];
	
	// Percorre todos os caracteres da parte da entrada
	for (i = 0; i < partLength; i++) {
		character = part[i];
		if(character == ' ' || character == '.' || character == ',' || character == '\r' || character == 9 || character == '!' || character == '?' || character == ';') {
			// Finaliza a palavra pois achou marcador final de palavra
			word[wordIndex] = '\0';
			// Verifica palindromo de palavra
			if(checkPalindrome(word))
				words++;
			wordIndex = 0;
			
			// Finaliza a frase pois achou marcador finalizador de frase
			if(character == '.' || character == '!' || character == '?' || character == ';') {
				phrase[phraseIndex] = '\0';
				// Verifica palindromo de frase
				if(checkPalindrome(phrase))
					phrases++;
				phraseIndex = 0;
			} 
		} 
		// A funcao strchr checa se existe a ocorrencia de um caractere em uma string - retorna o ponteiro da primeira ocorrencia
		// Logo, se o caractere nao esta contido na lista de ignorados, ele pode ser adicionado a palavra ou frase
		else if (strchr(ignoreList, character) == NULL) {
			// Transforma as letras em minusculas
			if (character >= 'A' && character <= 'Z')
				character += 'a' - 'A';
			// Adiciona o caractere a palavra e a frase
			word[wordIndex++] = character;
			phrase[phraseIndex++] = character;
		}
	}
	// O for terminara quando a entrada acabar, porem a ultima palavra tem que ser finalizada para nao ser perdida
	word[wordIndex] = phrase[phraseIndex] = '\0';
	// Verifica palindromos
	if(checkPalindrome(word))
		words++;
	if(checkPalindrome(phrase))
		phrases++;
	// Atualiza parametros passados por referencia para que possam ser utilizados fora da funcao
	*wordCount = words;
	*phraseCount = phrases;
}

int main() {
	// Marca o tempo inicial da execucao
	double startingTime, totalTime;
	startingTime = omp_get_wtime();
	
	// Atribui o numero de threads a ser utilizados com a OpenMP
	omp_set_num_threads(NTHREADS);
	
	int i, index, previousIndex, palindromeWordCount = 0, palindromePhraseCount = 0, partWordCount, partPhraseCount, fileSize;
	FILE *file;
	char *fileBuffer, *parts[NTHREADS];
	
	puts("\nArquivo menor (shakespe.txt):");
	// Abre o arquivo com o modificador "r", que eh de leitura
	file = fopen("data/shakespe.txt", "r");
	if (file == NULL) {
		puts("Erro ao abrir o arquivo:");
		perror("data/shakespe.txt");
	} else {	
		// Move o cursor para o fim do arquivo
		fseek(file, 0, SEEK_END);
		// Descobre o tamanho do arquivo
		fileSize = ftell(file);
		// Volta o cursor para o comeco
		fseek(file, 0, SEEK_SET);
		// Aloca memoria para o buffer
		fileBuffer = (char *)calloc(sizeof(char), fileSize);
		// Le o arquivo inteiro e passa o conteudo para o vetor alocado
		fread(fileBuffer, sizeof(char), fileSize, file);

		// Divide o conteudo do arquivo em varias partes (de acordo com o numero de threads a ser utilizado)
		previousIndex = 0;
		for(i = 0; i < NTHREADS; i++) {
			// Coloca um marcador na posicao final da parte a ser coletada
			index = (i + 1) * fileSize / NTHREADS;
			// Procura por um ponto ou espaco proximo, para palavras nao ficarem incompletas
			while (index != strlen(fileBuffer) && fileBuffer[index] != '.' && fileBuffer[index] != ' ')
				index++;
			// Aloca a memoria para a parte do arquivo de entrada
			parts[i] = (char *)calloc(sizeof(char), index - previousIndex);
			// Copia o trecho do buffer para o vetor alocado
			strncpy(parts[i], fileBuffer + previousIndex, index - previousIndex);
			// Atualiza o marcador de comeco da parte
			previousIndex = index;
		}
		
		// Implementa a paralelizacao do OpenMP, paralelizando o loop. A reducao eh feita pois existe a soma dos valores locais de palindromos de palavras e frases
		#pragma omp parallel for reduction(+ : palindromeWordCount, palindromePhraseCount)
		// Para cada parte de entrada
		for (i = 0; i < NTHREADS; i++) {
			// Chama a funcao que conta palindromos na parte, os ints de contadores sao passados por referencia, entao podem ser utilizados posteriormente
			countPalindromesInPart(parts[i], &partWordCount, &partPhraseCount);
			// Atualiza a contagem global
			palindromeWordCount += partWordCount;
			palindromePhraseCount += partPhraseCount;
		}
		// Imprime resultados
		printf("Por palavras: %d palindromos\n", palindromeWordCount);
		printf("Por frases: %d palindromos\n", palindromePhraseCount);
	
		// Fecha arquivo e libera memoria
		fclose(file);
		free(fileBuffer);
		for (i = 0; i < NTHREADS; i++)
			free(parts[i]);
	
		// Computa o tempo final e calcula o tempo decorrido de execucao
		totalTime = omp_get_wtime() - startingTime;
		printf("Tempo: %.2lfs\n\n", totalTime);
	}
	return 0;
}
